嗨大家好今天是第五天,這次主題會切成上下兩部分
在“JavaScript設計模式與開發實踐”書中是說發佈/訂閱模式又叫做“觀察者模式”,但我查了一下其中好像有些差別,在書中定義了發佈/訂閱模式:
物件之間的一對多依賴關係,當一個物件狀態發生改變時,所有依賴於它的物件都得到通知。
我們可以用現實生活中的例子來說明。當你去買東西的時候,你要的款式沒了,這時候就會跟銷售業務員訂貨,你會留下你的電話讓業務員在貨到的時候打電話給你,而不是你每天都打電話問一下貨到了沒有。這樣的行為就是一種發佈/訂閱模式,實際在網頁上DOM節點綁定事件就是採用這種模式:
document.body.addEventListener('click',fn)
接下來我們將自己實作發佈/訂閱模式,我們以剛剛的買東西案子為例:
首先我們要先指定誰是發佈者(業務部門)
var saleOffices = {};
再來我們給發佈者做一個陣列,用來存放訂閱者(客戶)的資訊
saleOffices.clientList = [];
接下來實作註冊方法,有人訂閱時,把訂閱者提供的通知方法(你留的電話)放到陣列中,程式裡我們會帶入callback function當作通知的方法
saleOffices.listen = function (fn) {
this.clientList.push(fn);
};
那當要發佈時(貨到的時候)就把陣列存放的function拿出來執行(打電話給你)
saleOffices.trigger = function () {
for (var i = 0, fn; fn = this.clientList[i++];) {
fn.apply(this, arguments);
}
};
實際訂閱一下,小胖與夥伴都訂貨
saleOffices.listen(function (name, price) {
console.log('小胖的商品到囉:', name, price+'元');
});
saleOffices.listen(function (name, price) {
console.log('帥氣夥伴的商品到囉:', name, price+'元');
});
一行通知所有訂貨的人說貨到了
saleOffices.trigger('減肥套餐', 100000);
以上就是簡單的發佈/訂閱模式,但上面這種寫法會無法區分出來訂不同的商品,我們必須讓訂閱者只訂閱自己感興趣的訊息,所以我們再改寫一下:
還是一樣的發佈者,注意clientList已經不再是陣列了
var saleOffices = {};
saleOffices.clientList = {};
在實作註冊部分時加入key參數,代表每個訂閱者的商品名稱,那一樣商品名稱的訂閱者callback function要用陣列一起裝起來
saleOffices.listen = function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
};
那在實作發佈的時候一樣只針對這次到到的產品(key)來發佈
saleOffices.trigger = function () {
var key = Array.prototype.shift.call(arguments);
var fnPool = this.clientList[key];
if (!fnPool || fnPool.length === 0) {
return false;
}
for (var i = 0, fn; fn = fnPool[i++];) {
fn.apply(this, arguments);
}
};
那再實際訂閱一下,小胖訂了A產品以及B產品,夥伴只訂了A產品
saleOffices.listen('A', function (name, price) {
console.log('小胖訂的A貨到了:', name, price);
});
saleOffices.listen('B', function (name, price) {
console.log('小胖訂的B貨到了:', name, price);
});
saleOffices.listen('A', function (name, price) {
console.log('夥伴訂的A貨到了:', name, price);
});
這時候發佈B產品貨到的話就只會通知小胖囉
saleOffices.trigger('B', 'BBB', 100000);
那我們這樣寫的時候可能會發現廠商不只一家,那假設有個saleOffices2的話難道要全部複製一次嗎?
別,我們再改改:
首先我們做個通用的物件,裏面一樣具有之前上面範例所講的clientList、listen、trigger功能(內容實作都一樣),我們額外多做了一個刪除註冊事件的功能(不是重點)
var event = {
clientList: {},
listen: function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function () {
var key = Array.prototype.shift.call(arguments);
var fnPool = this.clientList[key];
if (!fnPool || fnPool.length === 0) {
return false;
}
for (var i = 0, fn; fn = fnPool[i++];) {
fn.apply(this, arguments);
}
},
remove : function (key, removeFn) {
var fnPool = this.clientList[key];
if (!fnPool || fnPool.length === 0) {
return false;
}
if (!removeFn) {
fnPool && (fnPool.length = 0);
} else {
for (var i = 0, fn; fn = fnPool[i]; i++) {
if (fn === removeFn) {
fnPool.splice(i, 1);
}
}
}
}
};
接下來我們做一個安裝事件的功能,用意就是讓每個物件(各家廠商)都可以具有這個功能,實際作法就只是把event物件每個元素都指定到你的物件上
var installEvent = function (obj) {
for (var i in event) {
obj[i] = event[i];
}
};
實際用法都跟之前的範例一樣,只是要先用installEvent"安裝"。
var saleOffices = {};
installEvent(saleOffices);
saleOffices.listen('X', function (name, price) {
console.log('A:', name, price);
});
saleOffices.trigger('X', 'X商品', 100000);